package ink.tsg.manydatasource.aspect;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.JSON;
import ink.tsg.manydatasource.config.DynamicDataSource;
import ink.tsg.manydatasource.entity.UserDb;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.GetMapping;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * @author tsg
 * @version 1.0
 * @description: TODO
 * @date 2022/5/5 10:03
 */
@Aspect
@Component
@Slf4j
public class DynamicAspect {
    /**
     * 定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码.
     * 使用 @Pointcut 来声明切入点表达式.
     * 后面的其他通知直接使用方法名来引用当前的切入点表达式.
     * （..）表示任意参数
     */
    @Pointcut("execution(* ink.tsg.manydatasource.controller..*.*(..))")
    public void executeService() {
    }


    //声明该方法执行之前执行，前置通知
    //直接导入切面点，上面的
    @Before("executeService()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        List<Object> list = Arrays.asList(args);
        UserDb userDb = JSON.parseObject(JSON.toJSONString(list.get(0)), UserDb.class);
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(userDb.getJdbcUrl());
        druidDataSource.setUsername(userDb.getUsername());
        druidDataSource.setPassword(userDb.getPassword());
        druidDataSource.setDriverClassName(userDb.getDriverClass());
        DynamicDataSource.dataSourcesMap.put(userDb.getUsername(), druidDataSource);
        DynamicDataSource.setDataSource(userDb.getUsername());
//        此时数据源已切换到druidDataSource ，调用自己的业务方法即可。

        log.info("beforeMethod()--->The method " + methodName + " begins with " + Arrays.asList(args));
    }

    //后置通知：在目标方法执行后（无论是否发生异常），执行通知
    //在后置通知中还不能访问目标方法的执行的结果，不是在执行方法后调用的

    /**
     * 这是切面开始打印出来的--->The method add begins with [3, 5]
     * 这是切面结束打印出来的--->The method add ends
     * 和--->8
     */
    @After("executeService()")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();

        log.info("afterMethod()--->使用完后调用DynamicDataSource.clear(),重置为默认数据源 " + methodName + " ends");
        // 使用完后调用,重置为默认数据源。
        DynamicDataSource.clear();
        log.info("afterMethod()--->The method " + methodName + " ends");
    }


    /**
     * 带有返回值的切面
     * 在方法法正常结束受执行的代码
     * 返回通知是可以访问到方法的返回值的!
     * 可以使用returning = "result"进行获取后得到
     */
    @AfterReturning(value = "executeService()",
            returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("afterReturning()--->The method " + methodName + " ends with " + result);
    }

    /**
     * 异常处理切面
     * 在目标方法出现异常时会执行的代码.
     * 可以访问到异常对象; 且可以指定在出现特定异常时在执行通知代码
     */
    @AfterThrowing(value = "executeService()",
            throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        log.info("afterThrowing()--->The method " + methodName + " occurs excetion:" + e);
    }

    /**
     * 环绕切面，类似于动态代理，可以包含前面4种的任意个
     * 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
     * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
     * 且环绕通知必须有返回值, 返回值即为目标方法的返回值
     */

    @Around("executeService()")
    public Object aroundMethod(ProceedingJoinPoint pjd) {

        Signature signature = pjd.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String methodName = method.getName();
        // 获取GetMapping注解的value
        GetMapping getMapping = method.getAnnotation(GetMapping.class);
        Object result = null;
        try {
            //执行目标方法
            result = pjd.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        if (!ObjectUtils.isEmpty(getMapping)) {
            String[] value = getMapping.value();
            log.info("获取GetMapping注解的value：" + value[0]);
            System.out.println("aroundMethod()--->The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
            //执行目标方法
            //返回通知
            System.out.println("aroundMethod()--->The method " + methodName + " ends with " + result);
        }
        //后置通知
        System.out.println("aroundMethod()--->The method " + methodName + " ends");
        return result;
    }
}
